package com.googlecode.pacswitch.actions;

import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;

import com.googlecode.pacswitch.Action;
import com.googlecode.pacswitch.CloseOnCompleteHandler;
import com.googlecode.pacswitch.Log;
import com.googlecode.pacswitch.ObjectFactory;
import com.googlecode.pacswitch.PInteger;
import com.googlecode.pacswitch.PString;
import com.googlecode.pacswitch.PacSwitch;

/**
 * @author Andrey Pavlenko
 */
@XmlType(name = "Response", namespace = ObjectFactory.ANS, propOrder = {
        "headers", "body" })
public class Response implements Action {
    private final List<Header> _headers = Collections.checkedList(
            new ArrayList<Header>(3), Header.class);
    private PString _version;
    private PInteger _statusCode;
    private PString _reason;
    private PString _body;

    @XmlAttribute
    public PString getVersion() {
        return _version;
    }

    public void setVersion(final PString version) {
        _version = version;
    }

    @XmlAttribute
    public PInteger getStatusCode() {
        return _statusCode;
    }

    public void setStatusCode(final PInteger statusCode) {
        _statusCode = statusCode;
    }

    @XmlAttribute
    public PString getReason() {
        return _reason;
    }

    public void setReason(final PString reason) {
        _reason = reason;
    }

    @XmlElement(name = "body", namespace = ObjectFactory.ANS)
    public PString getBody() {
        return _body;
    }

    public void setBody(final PString body) {
        _body = body;
    }

    @XmlElement(name = "header", type = Header.class,
        namespace = ObjectFactory.ANS)
    public List<Header> getHeaders() {
        return _headers;
    }

    @Override
    public void perform(final PacSwitch ps,
            final AsynchronousSocketChannel clientSocket) {
        final Log log = ps.getConfig().getLog();
        final String response = createResponse(ps, clientSocket);
        final ByteBuffer b = ByteBuffer.wrap(response.getBytes());
        final CloseOnCompleteHandler handler = new CloseOnCompleteHandler(ps,
                clientSocket);

        if (log.isDebugEnabled()) {
            log.debug("Sending response:\r\n" + response);
        }

        clientSocket.write(b, 30, TimeUnit.SECONDS, null, handler);
    }

    protected List<Header> getDefaultHeaders() {
        return Collections.emptyList();
    }

    protected String resolveVersion(final PacSwitch ps,
            final AsynchronousSocketChannel clientSocket) {
        final PString version = getVersion();
        return (version == null) ? "1.1" : version.resolve(ps, clientSocket);
    }

    protected int resolveStatusCode(final PacSwitch ps,
            final AsynchronousSocketChannel clientSocket) {
        final PInteger statusCode = getStatusCode();
        return (statusCode == null) ? 200 : statusCode.resolveInt(ps,
                clientSocket);
    }

    protected String resolveReason(final PacSwitch ps,
            final AsynchronousSocketChannel clientSocket) {
        final PString reason = getReason();
        return (reason == null) ? "OK" : reason.resolve(ps, clientSocket);
    }

    protected String resolveBody(final PacSwitch ps,
            final AsynchronousSocketChannel clientSocket) {
        final PString body = getBody();
        return (body == null) ? null : body.resolve(ps, clientSocket);
    }

    protected String createResponse(final PacSwitch ps,
            final AsynchronousSocketChannel clientSocket) {
        final String version = resolveVersion(ps, clientSocket);
        final int statusCode = resolveStatusCode(ps, clientSocket);
        final String reason = resolveReason(ps, clientSocket);
        final String body = resolveBody(ps, clientSocket);
        final StringBuilder sb = new StringBuilder();

        sb.append("HTTP/").append(version).append(' ').append(statusCode)
                .append(' ').append(reason).append("\r\n");

        for (final Header h : getHeaders()) {
            sb.append(h.getName().resolve(ps, clientSocket)).append(": ")
                    .append(h.getValue().resolve(ps, clientSocket))
                    .append("\r\n");
        }

        for (final Header h : getDefaultHeaders()) {
            sb.append(h.getName().resolve(ps, clientSocket)).append(": ")
                    .append(h.getValue().resolve(ps, clientSocket))
                    .append("\r\n");
        }

        if ((body != null) && (body.length() > 0)) {
            sb.append("Content-Length: ").append(body.length())
                    .append("\r\n\r\n").append(body);
        } else {
            sb.append("\r\n");
        }

        return sb.toString();
    }

    @XmlType(name = "Header")
    public static class Header {
        private PString _name;
        private PString _value;

        public Header() {
        }

        public Header(final String name, final String value) {
            this(new PString(name), new PString(value));
        }

        public Header(final PString name, final PString value) {
            _name = name;
            _value = value;
        }

        @XmlAttribute(required = true)
        public PString getName() {
            return _name;
        }

        public void setName(final PString name) {
            _name = name;
        }

        @XmlAttribute(required = true)
        public PString getValue() {
            return _value;
        }

        public void setValue(final PString value) {
            _value = value;
        }
    }
}
